package parser;

import java.util.ArrayList;
import java.util.List;

public class Parser {
	// List<Root> is returned to handle the case of | operator, which selects
	// several paths and takes the union of results of multiple independent
	// queries
	public static List<Root> parse(String query) {
		List<Root> rootList = new ArrayList<Root>();

		// String.split() takes in regular expression token as delimiter
		String[] rootQueries = query.split("\\|");
		for (String rootQuery : rootQueries) {
			rootQuery = rootQuery.trim();
			rootList.add(parseRoot(rootQuery));
		}

		return rootList;
	}

	private static Root parseRoot(String query) {
		boolean isTopLevel = false;
		boolean isAttribute = false;
		Root root;

		// Check the properties of root node of query
		if (query.charAt(0) == '/') {
			if (query.charAt(1) == '/') {
				query = query.substring(2);
			} else {
				isTopLevel = true;
				query = query.substring(1);
			}
		}

		if (query.charAt(0) == '@') {
			isAttribute = true;
			query = query.substring(1);
		}

		String key = getNextKey(query);
		String type, literal;

		if (key == null) {
			literal = query;
		} else if (key == "[") {
			int index = findRightBracket(query);
			literal = query.substring(0, index + 1);
			query = query.substring(index + 1);
		} else {
			int index = query.indexOf(key);
			literal = query.substring(0, index);
			query = query.substring(index);
		}

		String[] splitResult = split(literal);
		type = splitResult[0];
		literal = splitResult[1];

		// Create root node of the query
		if (isAttribute)
			root = new Root(type, null, isTopLevel);
		else
			root = new Root(type, isTopLevel);

		processSpecs(root, literal);
		Node resultNode = processPath(root, query, true);
		resultNode.setOutput(true);
		root.setResult(resultNode);
		return root;
	}

	private static String[] split(String query) {
		int index = query.indexOf('[');
		return index == -1 ? new String[] { query.trim(), "" } : new String[] {
				query.substring(0, index), query.substring(index).trim() };
	}

	private static Node processPath(Node parent, String query, boolean isInPath) {
		String key = getNextKey(query);
		String literal, type;
		String[] splitResult;

		while (key != null) {
			if (key.equals("//")) {
				query = query.substring(2);
				key = getNextKey(query);
				if (key == null) {
					literal = query;
					splitResult = split(literal);
					type = splitResult[0];
					literal = splitResult[1];
					Node descendant = type.charAt(0) == '@' ? new Node(
							type.substring(1), null) : new Node(type);
					descendant.setInPath(isInPath);
					if (!isInPath)
						isInPath = true;
					parent.addDecendant(descendant);
					processSpecs(descendant, literal);
					return descendant;
				} else if (key.equals("[")) {
					int index = findRightBracket(query);
					literal = query.substring(0, index + 1);
					query = query.substring(index + 1);
					splitResult = split(literal);
					type = splitResult[0];
					literal = splitResult[1];
					Node descendant = new Node(type);
					descendant.setInPath(isInPath);
					if (!isInPath)
						isInPath = true;
					parent.addDecendant(descendant);
					processSpecs(descendant, literal);
					parent = descendant;
					key = getNextKey(query);
				} else {
					int index = query.indexOf(key);
					literal = query.substring(0, index);
					query = query.substring(index);
					splitResult = split(literal);
					type = splitResult[0];
					literal = splitResult[1];
					Node descendant = new Node(type);
					descendant.setInPath(isInPath);
					if (!isInPath)
						isInPath = true;
					parent.addDecendant(descendant);
					processSpecs(descendant, literal);
					parent = descendant;
				}
			} else if (key.equals("/")) {
				query = query.substring(1);
				key = getNextKey(query);
				if (key == null) {
					literal = query;
					splitResult = split(literal);
					type = splitResult[0];
					literal = splitResult[1];
					Node child = type.charAt(0) == '@' ? new Node(
							type.substring(1), null) : new Node(type);
					child.setInPath(isInPath);
					if (!isInPath)
						isInPath = true;
					parent.addChild(child);
					processSpecs(child, literal);
					return child;
				} else if (key.equals("[")) {
					int index = findRightBracket(query);
					literal = query.substring(0, index + 1);
					query = query.substring(index + 1);
					splitResult = split(literal);
					type = splitResult[0];
					literal = splitResult[1];
					Node child = new Node(type);
					child.setInPath(isInPath);
					if (!isInPath)
						isInPath = true;
					parent.addChild(child);
					processSpecs(child, literal);
					parent = child;
					key = getNextKey(query);
				} else {
					int index = query.indexOf(key);
					literal = query.substring(0, index);
					query = query.substring(index);
					splitResult = split(literal);
					type = splitResult[0];
					literal = splitResult[1];
					Node child = new Node(type);
					child.setInPath(isInPath);
					if (!isInPath)
						isInPath = true;
					parent.addChild(child);
					processSpecs(child, literal);
					parent = child;
				}
			}
		}
		return parent;
	}

	private static int findRightBracket(String query) {
		int leftIndex = query.indexOf('[');
		int rightIndex = query.indexOf(']');

		int index = query.indexOf('[', leftIndex + 1);

		while ((index != -1 && index < rightIndex)) {
			rightIndex = query.indexOf(']', rightIndex + 1);
			leftIndex = index;
			index = query.indexOf('[', leftIndex + 1);
		}

		return rightIndex;
	}

	private static void processSpecs(Node parent, String specs) {
		int index;

		if (specs.length() != 0) {
			specs = specs.substring(1, specs.length() - 1);

			int andIndex = specs.indexOf(" and ");
			int leftIndex = specs.indexOf('[');

			String[] subAttributes = { specs };
			if (andIndex != -1) {
				if (leftIndex != -1) {
					int rightIndex = findRightBracket(specs
							.substring(leftIndex));
					if (andIndex > rightIndex && andIndex < leftIndex) {
						subAttributes = specs.split(" and ");
					}
				} else
					subAttributes = specs.split(" and ");
			}

			for (String sub : subAttributes) {
				sub = sub.trim();
				Node newNode;
				if (sub.charAt(0) == '@') {
					sub = sub.substring(1);
					index = sub.indexOf('=');
					if (index != -1) {
						newNode = new Node(sub.substring(0, index).trim(), null);
						newNode.addChild(new Node(Constant.PREFIX
								+ sub.substring(index + 1).trim()));
					} else
						newNode = new Node(sub.trim(), null);
					parent.addChild(newNode);
				} else {
					String inputsub = sub.charAt(0) == '/' ? sub : "/" + sub;
					processPath(parent, inputsub, false);
				}
			}
		}
	}

	private static String getNextKey(String query) {
		int doubleIndex = query.indexOf("//");
		int singleIndex = query.indexOf("/");
		int bracketIndex = query.indexOf("[");

		if (doubleIndex == -1 && singleIndex == -1 && bracketIndex == -1)
			return null;
		else if (doubleIndex == -1)
			if (bracketIndex == -1)
				return "/";
			else if (singleIndex == -1)
				return "[";
			else
				return singleIndex < bracketIndex ? "/" : "[";
		else if (singleIndex == -1)
			if (bracketIndex == -1)
				return "//";
			else
				return "[";
		else if (bracketIndex == -1)
			return singleIndex < doubleIndex ? "/" : "//";
		else {
			int min = doubleIndex;
			String token = "//";
			if (singleIndex < min) {
				min = singleIndex;
				token = "/";
			}
			if (bracketIndex < min) {
				min = bracketIndex;
				token = "[";
			}

			return token;
		}
	}
}